home *** CD-ROM | disk | FTP | other *** search
/ The Atari Compendium / The Atari Compendium (Toad Computers) (1994).iso / files / prgtools / programm.ing / progem.lzh / wind5.prf < prev    next >
Encoding:
Text File  |  1987-06-23  |  17.5 KB  |  356 lines

  1. .!****************************************************************************
  2. .! 
  3. .! ANTIC PUBLISHING INC., COPYRIGHT 1985.  REPRINTED BY PERMISSION.
  4. .!
  5. .! ** Professional GEM ** by Tim Oren
  6. .!
  7. .! Proff File by ST enthusiasts at
  8. .! Case Western Reserve University
  9. .! Cleveland, Ohio
  10. .! uucp : decvax!cwruecmp!bammi
  11. .! csnet: bammi@case
  12. .! arpa : bammi%case@csnet-relay
  13. .! compuserve: 71515,155
  14. .!
  15. .!****************************************************************************
  16. .!
  17. .!
  18. .!****************************************************************************
  19. .!
  20. .!            Begin Part 5
  21. .!
  22. .!****************************************************************************
  23. .!
  24. .PART V Resource Tree Structures
  25. .PP
  26. This is the fifth issue of ST PROFESSIONAL GEM, concluding our
  27. trek through GEM dialogs and resources with a look at the internal
  28. structure of object trees.  Also, I'll answer a number of questions
  29. of general interest which have been received via the ANTIC ONLINE
  30. FEEDBACK.  As always, there is a download file associated with this
  31. column: GEMCL5.C, which you will find in DL3 of the new Atari 16-bit
  32. SIG (type GO PCS-58 or GO ATARI16).
  33. .PP
  34. Even if you have no immediate use for this issue's code, be sure
  35. to take the download anyway; some of the routines will be used in
  36. later articles.
  37. .PP
  38. In the last installment, we established that resources trees are
  39. pointed to by the tree index, and that they are composed of objects
  40. which contain pointers onward to other structures.  However, we
  41. passed over the issue of linkage among the objects within a tree.  It
  42. is now time to go back and cure this omission.
  43. .PP
  44. The technical term for the linkage scheme of an object tree is a
  45. "right-threaded binary tree".   If you already know what this is, you
  46. can skim over the next few paragraphs.  If you happen to have access
  47. to a copy of the book "FUNDAMENTAL ALGORITHMS", which is part of the
  48. series THE ART OF COMPUTER PROGRAMMING by Donald E. Knuth, you might
  49. want to read his excellent discussion of binary trees beginning on
  50. page 332.
  51. .PP
  52. For the following discussion, you should have a listing of the C
  53. image of a resource tree in front of you.  For those who do not have
  54. the listing from the last column, I have included a fragment at the
  55. beginning of the download.  Before we begin, I should warn you of one
  56. peculiarity of "computer trees":  They grow upside-down!  That is,
  57. when they are diagrammed or described, their root is at the top, and
  58. the  "leaves" grow downward.  You will see this both in the listing,
  59. and in the way the following discussion talks about moving through
  60. trees.
  61. .PP
  62. Each GEM object tree begins at its ROOT object, numbered zero,
  63. which is the object pointed at by the tree index.  There are three
  64. link  fields at the beginning of each object.  They are called
  65. OB_NEXT, OB_HEAD,  and OB_TAIL, which is the order in which they
  66. appear.
  67. .PP
  68. Each of the links is shown as an index relative to the root of
  69. the current tree.  This means that the link '0' would refer to the
  70. root of the tree, while '2' would indicate the object two lines below
  71. it. The special link -1 is called NIL, and means that there is no
  72. link in  the given direction.
  73. .PP
  74. Each object, or node, in a tree may have "offspring" or nodes
  75. which are nested below it.  If it does, then its OB_HEAD will point
  76. to its first (or "leftmost") "child", while the OB_TAIL will point to
  77. the last ("rightmost") of its offspring.  The OB_NEXT pointer links
  78. the  children together, with the OB_NEXT of the first pointing to the
  79. second, and so on, until the OB_NEXT of the last finally points back
  80. to its parent, the object at which we started.
  81. .PP
  82. Remember that each of these children may in turn have offspring
  83. of their own, so that the original "parent" may have a large and
  84. complex collection of "descendents".
  85. .PP
  86. Let's look at the first tree in the download to see an example
  87. of this structure.  The very first object is the ROOT.  Note that its
  88. OB_NEXT is NIL, meaning that there are no more objects in the tree:
  89. the ROOT is both the beginning and the end of the tree.  In this
  90. case, the OB_HEAD is 1 and the OB_TAIL is 3, showing that there are
  91. at least two different children.
  92. .PP
  93. Following OB_HEAD down to the next line, we can trace through
  94. the OB_NEXT links (2, 3, 0) as they lead through a total of three
  95. children and back to the ROOT.  You will notice that the first two
  96. children have NIL for the OB_HEAD and OB_TAILs, indicating that they
  97. have no further offspring.
  98. .PP
  99. However, node three, the last child of the ROOT, does have the
  100. value 4 for both its OB_HEAD and OB_TAIL.  By this we can tell that
  101. it  has one, and only one, offspring.  Sure enough, when we look at
  102. node four, we see that its OB_NEXT leads immediately back to node
  103. three. Additionally, it has no further offspring because its OB_HEAD
  104. and OB_TAIL are NIL.
  105. .PP
  106. You will find that object trees are always written out by the
  107. Resource Construction Set in "pre-order".  (Again, see Knuth if you
  108. have a copy.)  This means that the ROOT is always written first, then
  109. its offspring left to right.  This rule is applied recursively, that
  110. is, we go down to the next level and write out each of these nodes,
  111. then THEIR children left to right, and so on.
  112. .PP
  113. For a further example, look at the next tree in rs_object in the
  114. download.  You will see that the ROOT has an OB_HEAD of 1 and an
  115. OB_TAIL of 6, but that it actually has only three offspring (nodes 1,
  116. 2 and 6).  We see that node 2 itself had children, and applying the
  117. rule given above, they were written out before continuing with the
  118. next child of the ROOT.
  119. .PP
  120. Why was this seemingly complex structure chosen for GEM?  The
  121. reason has do with the tasks of drawing objects in their proper
  122. locations on the screen, and determining which object was "hit" when
  123. a mouse click is detected.
  124. .PP
  125. To find out how this works, we must look at four more fields
  126. found in each object: OB_X, OB_Y, OB_WIDTH, and OB_HEIGHT.  These
  127. fields are the last four on each line in the sample trees.
  128. .PP
  129. Each object in a tree "owns" a rectangle on the screen.  These
  130. fields define that rectangle.  When a resource is stored "outside"
  131. the  program the fields are in character units, so that an object
  132. with OB_WIDTH  of 10 and OB_HEIGHT of 2 (for instance) would define a
  133. screen area 10  characters wide and 2 high.
  134. .PP
  135. When the resource is read into memory with an rsrc_load call,
  136. GEM multiplies the appropriate character dimension in pixels into
  137. each of these fields.  In this way portability is achieved: the same
  138. resource file works for any of the ST's three resolutions.  Knowing
  139. how rsrc_load works, your code should treat these fields as pixel
  140. coordinates.
  141. .PP
  142. I have committed one oversimplification above.  If an object is
  143. not created on a character boundary in the RCS, then the external
  144. storage method described will not work.  In this case, the lower byte
  145. of each rectangle field is used to store the nearest character
  146. position, while the upper byte stores the pixel remainder to be added
  147. after the character size is multiplied in.
  148. Non-character-boundary objects may only be created in the "FREE"
  149. tree mode of the Resource Construction Set (also called "PANEL" in
  150. RCS 2.0). You should use them only in programs which will run in a
  151. single ST screen  mode, because pixel coordinates are not portable
  152. between resolutions.
  153. .PP
  154. The first real secret of object rectangles is that each OB_X and
  155. OB_Y is specified RELATIVE to the X and Y coordinate of its parent
  156. object  within the tree.  This is the first property we have seen
  157. that is actually "inherited" from level to level within the tree.
  158. .PP
  159. The second secret is more subtle:  Every object's rectangle must
  160. be entirely contained within the rectangle of its parent.  This
  161. principle goes by the names "bounding rectangles" or "visual
  162. hierarchy".  We'll see in a moment how useful it is when detecting
  163. mouse/object collisions.
  164. .SH HOW GEM DOES IT.
  165. Knowing these secrets, and the linkage structure  of object
  166. trees, we can deduce how a number of the GEM operations must work.
  167. For instance, consider objc_offset,  which returns the actual screen
  168. X and Y  of an object.  We can see now that simply loading the OB_X
  169. and OB_Y fields of  the object does not suffice: they only give the
  170. offset relative to the parent  object.  So, objc_offset must BEGIN
  171. with these values, and then work its way  back up to the ROOT of the
  172. tree, adding in the offsets found at each level.
  173. .PP
  174. This can be done by following the OB_NEXT links from the chosen
  175. object.  Whenever OB_NEXT points to an object whose OB_TAIL points
  176. right back  to the same location, then the new node is another level,
  177. or "parent" in the  tree, and objc_offset adds its OB_X and OB_Y into
  178. the running totals.  When OB_NEXT becomes NIL, then the ROOT has been
  179. reached and the totals are the values to return.  (By the way,
  180. remember that the OB_X and OB_Y of the ROOT are undefined until
  181. form_center has been called for the tree.  They are shown as zeroes
  182. in the sample trees.)
  183. .PP
  184. We can also figure out objc_draw.  It works its way DOWN the
  185. tree, drawing each object as it comes to it.  It, too, must keep a
  186. running X and Y variable, adding in object offsets as it descends
  187. tree levels (using OB_HEAD), and subtracting them again as it returns
  188. from each level.   Since the larger objects are nearer the ROOT, we
  189. can now see why they are  drawn first, with smaller objects drawn
  190. later or "on top of" them.
  191. .PP
  192. If you write an application which needs to move portions of a
  193. dialog or screen with respect to each other, you can take advantage
  194. of inheritance of screen position in objc_draw.  Simply by changing
  195. the OB_X and/or OB_Y of an object, you can move it and its entire
  196. sub-tree to a new location in the dialog.  For instance, changing the
  197. coordinates of the parent box of a set of radio buttons will cause
  198. all of the buttons to move along with it.
  199. .PP
  200. Objc_draw also gives us an example of the uses of visual
  201. hierarchy. Recall that a clipping rectangle is specified when calling
  202. objc_draw. At each level of the tree we know that all objects below
  203. are contained in the screen rectangle of the current object.  If the
  204. current rectangle falls completely outside the specified clipping
  205. rectangle, we know  immediately that we need not draw the object, or
  206. any of its descendents! This ability to ignore an entire subtree is
  207. called "trivial rejection".
  208. .PP
  209. Now it's rather easy to figure out objc_find.  It starts out by
  210. setting its "object found" variable to NIL.  It begins a "walk"
  211. through the entire object tree, following OB_HEAD and OB_NEXT links,
  212. and keeping  a current X and Y, just like objc_draw.
  213. .PP
  214. At each node visited, it simply checks to see if the "mouse" X,Y
  215. specified in the call are inside the current object's rectangle.  If
  216. they  are, that object becomes the found object, and the tree walk
  217. continues with the object's offspring, and then siblings.  Notice how
  218. this checking of offspring makes sure that a smaller object nested
  219. within, i.e., below, a larger object is found correctly.
  220. .PP
  221. If the mouse X,Y position is not within the object being
  222. checked, then by visual hierarchy it cannot be within any of its
  223. offspring, either.   Trivial rejection wins again, and the entire
  224. sub-tree is skipped!  Objc_find  moves on to the OB_NEXT of the
  225. rejected object.
  226. .SH THOUGHT EXPERIMENTS
  227. Thinking about the objc_find algorithm  reveals some information
  228. about its performance, and a few tricks we may  use in improving the
  229. appearance of dialogs and other object trees.
  230. .PP
  231. First consider the problem of a dialog which contains many
  232. objects. If we lay them all out "side-by-side", then they will all be
  233. immediate offspring of the ROOT object.  In this situation, the
  234. trivial rejection method will gain nothing.  The time objc_find takes
  235. to complete will vary linearly with the total number of objects.
  236. This is called an "Order N" process.
  237. .PP
  238. Suppose that instead we broke up the dialog into two areas with
  239. invisible boxes, then broke up each of these areas in a like fashion,
  240. and so on until we got down to the size of the individual selectable
  241. objects.  The number of bottom level objects in this scheme is a
  242. power  of two equal to the depth of the tree. Trivial rejection is
  243. used to its  fullest in this case.  It is called an "Order Log N"
  244. process, and is much  more efficient for large numbers of objects.
  245. .PP
  246. In practice, the speed of the ST will allow you to ignore this
  247. distinction for most dialogs and other trees.  But if you get into a
  248. situation when speed is critical in searching a large tree, remember
  249. that nesting objects can improve performance dramatically.
  250. .PP
  251. If you have been following closely, you may have also noticed a
  252. hole in the visual hierarchy rule.  It says that all of a node's
  253. children must lie within its rectangle, but it does NOT guarantee
  254. that the children's  rectangles will be disjoint, that is, not
  255. overlap one another.  This peculiarity is the basis of several useful
  256. tricks.
  257. .PP
  258. First, remember that objc_find always tries to scan the entire
  259. tree.  That is, it doesn't quit when it finds the first object on the
  260. given coordinates.  As mentioned above, this normally guarantees that
  261. nested objects will be found.  Consider, however, what happens when
  262. the mouse  coordinates are on a point where two or more objects AT
  263. THE SAME LEVEL overlap: they will replace one another as the "found
  264. object" until  objc_find returns with the one which is "last", that
  265. is, rightmost in  the tree.
  266. .PP
  267. This quirk can be used to advantage in a number of cases.
  268. Suppose that you have in a dialog an image and a string which you
  269. would  like to be selected together when either is clicked.  Nesting
  270. within a common parent achieves nothing in this case.  Instead,
  271. knowing that  form_do must use objc_find, you could use our trick.
  272. .PP
  273. You have to know that the Resource Construction Set normally
  274. adds  objects in a tree left to right, in the order in which you
  275. inserted them.  You proceed to build the dialog in the following
  276. order: insert the image  first, the string next, then carefully add
  277. an invisible box which is not  nested within either, and size it to
  278. cover them both.  Set the SELECTABLE  attribute for the box, and the
  279. dialog manager will find it, and invert  the whole area, when either
  280. the image or string is clicked.
  281. .PP
  282. By the way, remember that the SORT option in the RCS will change
  283. the order of an object's offspring.  If you are going to try this
  284. trick, don't use SORT!  It will undo all of your careful work.
  285. .SH A TREEWALKER OF OUR OWN
  286. Since the GEM system gets so much mileage  out of walking object
  287. trees, it seems reasonable that the same method should  be useful in
  288. application programs.  In the download you will find map_tree(). As
  289. many LISP veterans might guess from the name, this code will traverse
  290. all or part of an object tree, applying a function to each node.  It
  291. also allows the function to return a true/false value specifying
  292. whether  the sub-tree below a particular node should be ignored.
  293. Let's examine map_tree() in more detail as a final review of object
  294. tree structure.
  295. .PP
  296. First, look at the parameters.  "tree" is the long address of
  297. the object tree of interest, as retrieved by rsrc_gaddr.  "this" is
  298. the node at which to begin the traverse, and "last" is the node at
  299. which to terminate.
  300. .PP
  301. In most cases, the beginning node will be ROOT, and the final
  302. value will be NIL.  This will result in the entire tree being
  303. traversed. You may use other values, but be sure that you CAN get to
  304. "last" from "this" by following tree links!  Although map_tree()
  305. includes a safety  check to prevent "running off" the tree, you could
  306. get some very strange  results from incorrect parameters.
  307. .PP
  308. The declaration for the final parameter, "routine", makes use of
  309. C construct which may be new to some.  It is a pointer to a
  310. subroutine which returns a WORD as a result.
  311. .PP
  312. Map_tree() begins by initializing a temporary variable, tmp1,
  313. which is used to store the number of the last node visited.  Since no
  314. node will follow itself, setting tmp1 to the starting node is safe.
  315. .PP
  316. The main loop of the routine simply repeats visiting a new node
  317. until the last value is reached, or the safety check for end of tree
  318. is satisfied.
  319. .PP
  320. At any node visited, we can be in one of two conditions. Either
  321. we are at a node which is "new", that is, not previously visited, or
  322. else we are returning to a parent node which has already been
  323. processed. We can detect the latter condition by comparing the last
  324. node visited (tmp1) with the OB_TAIL pointer of the current node.  If
  325. the node is "old", it is not processed a second time, we simply
  326. update tmp1 and  continue.
  327. .PP
  328. If the node is new, we call "routine" to process it, sending the
  329. tree address and object number as parameters.  If a FALSE is
  330. returned,  we will ignore any subtree below this node.  On a TRUE
  331. return, we load up  the OB_HEAD pointer and follow it if a subtree
  332. exists.  (If you don't care  about rejecting subtrees, simply remove
  333. the if condition.)  Finally, if  the new node had no subtree, or was
  334. rejected by "routine", we follow along  its OB_NEXT link to the next
  335. node.
  336. .PP
  337. A simple application of our new tool shows its power.  From a
  338. previous column you may recall the tedium of deselecting every button
  339. inside a dialog after it was completed.  Using map_tree(), you can
  340. deselect EVERY OBJECT in the tree by using map_tree(tree, ROOT, NIL,
  341. desel_obj); You must use a slightly modified version of desel_obj()
  342. (included in the download) which always returns TRUE.  Be sure to
  343. define or declare desel_obj() in your code BEFORE making the map_tree
  344. call!
  345. .PP
  346. In future columns, I will return to map_tree() and show how it
  347. can be used for advanced techniques such as animated dialogs.  In the
  348. meantime, experiment and enjoy!
  349. .!
  350. .!
  351. .!*****************************************************************************
  352. .!*                                          *
  353. .!*                End Part 5                      *
  354. .!*                                          *
  355. .!*****************************************************************************
  356.